<?php
/**
 * @author Jumper Swordwave
 * date: 2021/04/21
 */
namespace App\Game\Logic;

/**
 * 
 */
final class Poker 
{
    const NONE = 0;     //不符合规则
    const DAN = 1;      //单张
    const DUI = 2;      //对子
    const THREE = 3;    //三张
    const SEQUE = 4;    //顺子
    const S_DUI = 5;     //连对
    const S_THREE = 6;   //连3张
    const THREE_ONE = 7;    //3带1
    const S_THREE_ONE = 8;  //3带1连
    const THREE_TWO = 9;   //3带2
    const S_THREE_TWO = 10; //3带2连
    const FOUR_ONE = 11;    //4带1
    const S_FOUR_ONE = 12;  //4带1连
    const FOUR_TWO = 13;    //4带2
    const S_FOUR_TWO = 14;  //4带2连

    const BOMB = 20;     //炸弹
    const KING_BOMB = 21;   //王炸

    //牌面值 
    const P3 = 3;          
    const P4 = 4;
    const P5 = 5;
    const P6 = 6;
    const P7 = 7;
    const P8 = 8;
    const P9 = 9;
    const P10 = 10;
    const PJ = 11;
    const PQ = 12;
    const PK = 13;
    const PA = 14;
    const P2 = 15;
    const PJ1 = 16; //小王
    const PJ2 = 17; //大王

    /**
     * 牌key值对应牌面值
     * 方便按key排序时由小到大显示牌面值,把相同的牌放一起,
     * 除去大小王外模4可得到花色(黑红草方)
     */
    private $pokers = [
        self::P3,self::P3,self::P3,self::P3,    //0,1,2,3
        self::P4,self::P4,self::P4,self::P4,    //4,5,6,7
        self::P5,self::P5,self::P5,self::P5,    //8,9,10,11
        self::P6,self::P6,self::P6,self::P6,    //12,13,14,15
        self::P7,self::P7,self::P7,self::P7,    //16,17,18,19
        self::P8,self::P8,self::P8,self::P8,    //20,21,22,23
        self::P9,self::P9,self::P9,self::P9,    //24,25,26,27
        self::P10,self::P10,self::P10,self::P10,//28,29,30,31
        self::PJ,self::PJ,self::PJ,self::PJ,    //32,33,34,35
        self::PQ,self::PQ,self::PQ,self::PQ,    //36,37,38,39
        self::PK,self::PK,self::PK,self::PK,    //40,41,42,43
        self::PA,self::PA,self::PA,self::PA,    //44,45,46,47
        self::P2,self::P2,self::P2,self::P2,    //48,49,50,51
        self::PJ1,self::PJ2,                    //52,53
    ];

    /**
     * 最大值
     */
    private $max_key = 53;

    /**
     * 发牌
     * 生成三副手牌和一堆底牌
     * @return array [$hands, $holes]
     */
    public function send() : array
    {
        $cards = [];
        for($i=0; $i<54; $i++) {
            $cards[] = $i;
        }
        shuffle($cards);
        $len = 17;
        $hands = [];
        $hands[] = array_slice($cards, 0, $len);
        $hands[] = array_slice($cards, $len, $len);
        $hands[] = array_slice($cards, 2 * $len, $len);
        $holes = array_slice($cards, 3 * $len);
        foreach($hands as &$hand) {
            sort($hand);
        }
        
        return [$hands, $holes];
    }

    /**
     * 检查出牌
     * @param array     $outs   玩家出牌
     * @param array     $pres   上家出牌
     * @return bool 出牌是否符合规则
     */
    public function checkOuts(array $outs, array $pres = []) : bool
    {
        $outs = $this->convertToVal($outs);
        $pres = $this->convertToVal($pres);
        
        list($out_type, $out_key, $out_len) = $this->getType($outs);

        if ($out_type == self::NONE) {  //出牌不符合规则
            return false;
        }

        if (empty($pres)) {  //没有上家出牌
            return true;
        }

        list($pre_type, $pre_key, $pre_len) = $this->getType($pres);
        \App\Log::info('check', ['type'=>[$out_type, $pre_type], 'key'=>[$out_key, $pre_key], 'len'=>[$out_len, $pre_len]]);
        
        if ($out_type == self::KING_BOMB) { //王炸,秒杀
            return true;
        }
        
        if ($out_type != $pre_type && $out_type == self::BOMB && $pre_type != self::KING_BOMB) {   //玩家出牌和上家类型不一样,但是炸弹并且上家不是王炸
            return true;
        }
        
        if ($out_type != $pre_type) {   //其它类型,出牌类型不一样则不符合出牌规则
            return false;
        }

        if ($out_len != $pre_len) { //类型一样,但长度不同,顺子,连对,飞机的长度不一样,不符合规则
            return false;
        }
        
        //出牌规则一致时,比较对应关键牌面大小
        return $out_key > $pre_key;
    }

    /**
     * 牌key转成牌面值
     * @param array $cards  牌key数组
     * @return array    牌面值数组
     */
    private function convertToVal(array $cards)
    {
        $result = [];
        foreach($cards as $key) {
            if ($key > $this->max_key) continue;    //非法牌
            $result[] = $this->pokers[$key];
        }
        return $result;
    }

    /**
     * 获取出牌类型
     * @param array $cards
     * @return array [出牌类型, 关键牌大小, 比较长度]
     */
    private function getType(array $cards)
    {
        //按出牌数量和大小排序
        $typefuncs = [
            self::DAN => 'isDan',
            self::KING_BOMB => 'isKingBomb',
            self::DUI => 'isDui',
            self::THREE => 'isThree',
            self::BOMB => 'isBomb',
            self::SEQUE => 'isSeque',
            self::S_DUI => 'isSDui',
            self::S_THREE => 'isSThree',
            self::THREE_ONE => 'isThreeOne',
            self::S_THREE_ONE => 'isSThreeOne',
            self::THREE_TWO => 'isThreeTwo',
            self::S_THREE_TWO => 'isSThreeTwo',
            self::FOUR_ONE => 'isFourOne',
            self::FOUR_TWO => 'isFourTwo',
        ];

        foreach($typefuncs as $key=>$func) {
            $r = $this->$func($cards);
            if ($r !== false) {
                return [$key, $r[0], $r[1]];
            }
        }

        return [self::NONE, null, 0];  //未找到符合规则
    }

    /**
     * 是否单张
     */
    private function isDan(array $cards)
    {
        if (count($cards) != 1) return false;
        return [$cards[0], 1,];
    }

    /**
     * 是否王炸
     */
    private function isKingBomb(array $cards)
    {
        if (count($cards) == 2 && $cards[0] == self::PJ1 && $cards[1] == self::PJ2) {
            return [$cards[1], 1];
        }
        return false;
    }

    /**
     * 是否一对
     */
    private function isDui(array $cards)
    {
        if (count($cards) == 2 && $cards[0] == $cards[1]) {
            return [$cards[0], 1,];
        }
        return false;
    }
    
    /**
     * 是否三张
     */
    private function isThree(array $cards)
    {
        if (count($cards) == 3 && $cards[0] == $cards[1] && $cards[1] == $cards[2]) {
            return [$cards[0], 1,];
        }
        return false;
    }

    /**
     * 是否炸弹
     */
    private function isBomb(array $cards)
    {
        if (count($cards) == 4 && $cards[0] == $cards[1] && $cards[1] == $cards[2] && $cards[2] == $cards[3]) {
            return [$cards[0], 1,];
        }
        return false;
    }
    
    /**
     * 是否顺子,
     * 5张以上,不能包含2,大小王
     */
    private function isSeque(array $cards)
    {
        $len = count($cards);
        if ($len < 5) return false;
        
        for($i=1; $i<$len; $i++)
        {
            if ($cards[$i] != $cards[$i-1] + 1 || $cards[$i] >= self::P2) {
                return false;
            }
        }
        return [$cards[0], $len];
    }

    /**
     * 是否连对,
     * 2对以上,不能包含2,大小王
     */
    private function isSDui(array $cards)
    {
        $len = count($cards);
        if ($len <= 2 || $len % 2 != 0) return false;
        
        for($i=1; $i<$len; $i+=2) {
            //不是对子或大于2
            if ($cards[$i] != $cards[$i-1] || $cards[$i] >= self::P2) {
                return false;
            }
            //不是连对
            if ($i > 1 && $cards[$i] != $cards[$i - 2] + 1) {
                return false;
            }
        }
        return [$cards[0], $len / 2];
    }
    
    /**
     * 是否飞机
     * 2个三张以上,不包含2,大小王
     */
    private function isSThree(array $cards)
    {
        $len = count($cards);
        if ($len <= 3 || $len % 3 != 0) return false;

        for($i=2; $i<$len; $i+=3) {
            if ($cards[$i] != $cards[$i - 1] || $cards[$i] != $cards[$i - 2]
                || $cards[$i] >= self::P2) {
                return false;
            }
            if ($i > 2 && $cards[$i] != $cards[$i - 3] + 1) {
                return false;
            }
        }
        return [$cards[0], $len / 3];
    }
    
    /**
     * 是否3带1
     */
    private function isThreeOne(array $cards)
    {
        if (count($cards) != 4) return false;

        //前三张或后三张相等
        if ($cards[0] == $cards[1] && $cards[1] == $cards[2]) {
            return [$cards[0], 1];
        }
        if ($cards[1] == $cards[2] && $cards[2] == $cards[3]) {
            return [$cards[1], 1];
        }
        return false;
    }

    /**
     * 是否3带1飞机
     */
    private function isSThreeOne(array $cards)
    {
        $len = count($cards);
        if ($len <= 4 || $len % 4 != 0) return false;

        $st_cards = [];
        for ($i=2; $i<$len;) {
            if ($cards[$i] == $cards[$i - 1] && $cards[$i] == $cards[$i - 2]) {
                $st_cards[] = $cards[$i];
                $st_cards[] = $cards[$i - 1];
                $st_cards[] = $cards[$i - 2];
                $i += 3;
            } else {
                $i++;
            }
        }
        $dan_cards = array_values(array_diff($cards, $st_cards));
        if (count($st_cards) / 3 != count($dan_cards)) {    //剩余单张和3连数量不同
            return false;
        }
        
        return $this->isSThree($st_cards);
    }

    /**
     * 是否三带二
     */
    private function isThreeTwo(array $cards)
    {
        if (count($cards) != 5) return false;

        if ($cards[0] == $cards[1] && $cards[1] == $cards[2] && $cards[3] == $cards[4]) {
            return [$cards[0], 1];
        }

        if ($cards[0] == $cards[1] && $cards[2] == $cards[3] && $cards[3] == $cards[4]) {
            return [$cards[2], 1];
        }

        return false;
    }

    /**
     * 是否三带二飞机
     */
    private function isSThreeTwo(array $cards)
    {
        $len = count($cards);
        if ($cards <= 5 || $len %5 != 0) return false;

        //找出其中的三张和对子分开存放
        $st_cards = [];
        $dui_cards = [];
        for($i=2; $i<$len;) {
            if ($cards[$i] == $cards[$i - 1] && $cards[$i] == $cards[$i - 2]) {
                $st_cards[] = $cards[$i];
                $st_cards[] = $cards[$i - 1];
                $st_cards[] = $cards[$i - 2];
                $i += 3;
            } else {
                $i++;
            }
        }
        $dui_cards = array_values(array_diff($cards, $st_cards));
        $dui_len = count($dui_cards);
        
        //三张数量和对子数量是否一致
        if (count($st_cards) / 3 != $dui_len / 2) {
            return false;
        }

        //是否都是对子
        for($i=1; $i<$dui_len; $i+=2){
            if ($dui_cards[$i] != $dui_cards[$i - 1]) {
                return false;
            }
        }
        
        //三张是否相连
        return $this->isSThree($st_cards);
    }
    
    /**
     * 是否四带一
     */
    private function isFourOne(array $cards)
    {
        if (count($cards) != 5) return false;

        if ($cards[0] == $cards[1] && $cards[1] == $cards[2] && $cards[2] == $cards[3]) {
            return [$cards[0], 1];
        }
        if ($cards[1] == $cards[2] && $cards[2] == $cards[3] && $cards[3] == $cards[4]) {
            return [$cards[1], 1];
        }
        
        return false;
    }

    /**
     * 是否四二
     */
    private function isFourTwo(array $cards)
    {
        if (count($cards) != 6) return false;
        
        if ($cards[0] == $cards[1] && $cards[1] == $cards[2] && $cards[2] == $cards[3]) {
            return [$cards[0], 1];
        }
        if ($cards[2] == $cards[3] && $cards[3] == $cards[4] && $cards[4] == $cards[5]) {
            return [$cards[2], 1];
        }
        return false;
    }
}